class Compiler {
  constructor(el, vue) {
    // 拷贝vue，加$符号，表示暴露给用户的
    this.$vue = vue
    this.$el = document.querySelector(el)
    if (this.$el) {
      // 在$fragment中操作，比this.$el操作节省很多性能
      // 将vue模板的地方使用片段代替，是为了便于在内存中操作
      let $fragment = this.node2Fragment(this.$el)
      this.compile($fragment)
      // 将替换好的片段追加到vue模板中
      this.$el.appendChild($fragment)
    }
  }
  node2Fragment(el) {
    // 将node节点放到fragment中
    var fragment = document.createDocumentFragment()
    var child
    // 将原生节点拷贝到fragment
    while (child = el.firstChild) {
      // 将el中的元素放到fragment中去,并删除el中原有元素，这个是appendChild自带的功能
      fragment.appendChild(child)
    }
    return fragment
  }
  compile(el) {
    var childNodes = el.childNodes
    var that = this
    childNodes.forEach(node => {
      var text = node.textContent
      var reg = /\{\{(.*)\}\}/
      if (node.nodeType === 1) {
        // 表示当前是html元素
        this.compileElement(node)
      } else if (node.nodeType === 3 && reg.test(text)) {
        // 当前为一个文本节点，并且文本节点包含{{}}
        that.compileText(node, RegExp.$1)
      }
    })
  }
  compileElement(node) {
    // compile用来对html元素进行编译，因为html元素上可能加了vue属性
    var nodeAttrs = node.attributes
    var that = this;
    // Element.Attributes属性返回该元素所有属性节点的一个事实集合
    // 该集合是一个NameNodeMap对象，不是数组，所以不能直接使用forEach
    [].slice.call(nodeAttrs).forEach(attr => {
      // 得到属性名称
      var attrName = attr.name
      // 得到属性值
      var exp = attr.value
      // 得到指令，如model，on：click
      var dir = attrName.substring(2)
      if (attrName.indexOf('v-') === 0) {
        // 为自定义的控件指令时
        if (dir.indexOf('on') === 0) {
          // 表示是点击事件的时候
          var fn = that.$vue.$options.methods && that.$vue.$options.methods[exp]
          node.addEventListener('click', fn.bind(that.$vue), false)
        } else if (dir.indexOf('model') === 0) {
          // 表示是绑定model值时
          new Watch(this.$vue, exp, function (value) {
            node.value = value
          })
          // 得到name在$vue中存放的值
          var val = that._getValue(that.$vue, exp)
          // 绑定值到input上
          node.value = val
          node.addEventListener('input', e => {
            var newValue = e.target.value
            if (val === newValue) {
              return
            }
            that._setVueVal(that.$vue, exp, newValue)
            val = newValue
          })
          node.removeAttribute(attrName)
        } else if (dir.indexOf('bind') === 0) {
          // 表示绑定class的样式时
          var className = node.className
          var value = this._getValue(that.$vue, exp)
          var space = className && String(value) ? ' ' : ''
          node.className = className + space + value
          new Watch(that.$vue, exp, function (value, oldValue) {
            var className = node.className
            className = className.replace(oldValue, '')
            var space = className && String(value) ? ' ' : ''
            node.className = className + space + value
          })
        }
      } else {}
    })
  }
  compileText(node, matchedName) {
    // 对包含可能出现vue标识的部分进行编译，主要是将{{XXX}}翻译成对应的值
    node.textContent = this._getValue(this.$vue, matchedName)
    new Watch(this.$vue, matchedName, function (value) {
      node.textContent = value
    })
  }
  // 将vue.a.b.c转换成vue[a][b][c]方式，获取对应值
  _getValue(vue, exp) {
    var val = vue
    exp = exp.split('.')
    exp.forEach(function (k) {
      val = val[k]
    })
    return val
  }
  _setVueVal(vue, exp, value) {
    var val = vue
    exp = exp.split('.')
    exp.forEach(function (k, i) {
      // 非最后一个key时，更新val的值
      if (i < exp.length - 1) {
        val = val[k]
      } else {
        val[k] = value
      }
    })
  }
}